/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007-2008 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.persistence.hibernate.bean.impl;

import java.util.Iterator;
import java.util.ListIterator;

import org.hibernate.ScrollableResults;

import com.genia.toolbox.basics.bean.IterableIterator;
import com.genia.toolbox.persistence.criteria.CriteriaResult;
import com.genia.toolbox.persistence.hibernate.criteria.HibernateCriteria;
import com.genia.toolbox.persistence.hibernate.criteria.HibernateCriteriaResult;

/**
 * Implementation of {@link IterableIterator} that allows to iterate over the
 * results of a query.
 * 
 * @param <MAINTYPE>
 *          the main type of the underlaying criteria
 */
public class HibernateResultIterableIterator<MAINTYPE>
    implements IterableIterator<CriteriaResult<MAINTYPE>>, ListIterator<CriteriaResult<MAINTYPE>>
{

  /**
   * the base iterator over the datas.
   */
  private final ScrollableResults scrollableResults;

  /**
   * the criteria that generated the results.
   */
  private final HibernateCriteria<MAINTYPE> hibernateCriteria;

  /**
   * the number of results.
   */
  private Integer size = null;

  /**
   * the position of the iterator.
   */
  private Integer position = 0;



  /**
   * constructor.
   * 
   * @param hibernateCriteria
   *          the criteria that generated the results
   * @param scrollableResults
   *          the base iterator over the datas
   */
  public HibernateResultIterableIterator(final HibernateCriteria<MAINTYPE> hibernateCriteria, final ScrollableResults scrollableResults)
  {
    this.hibernateCriteria = hibernateCriteria;
    this.scrollableResults = scrollableResults;
    if (!scrollableResults.first()) {
      scrollableResults.close();
      this.size = 0;
    }
  }



  /**
   * Returns an iterator over a set of elements of type T.
   * 
   * @return an Iterator.
   * @see java.lang.Iterable#iterator()
   */
  public Iterator<CriteriaResult<MAINTYPE>> iterator()
  {
    return this;
  }



  /**
   * Returns <tt>true</tt> if the iteration has more elements. (In other
   * words, returns <tt>true</tt> if <tt>next</tt> would return an element
   * rather than throwing an exception.)
   * 
   * @return <tt>true</tt> if the iterator has more elements.
   * @see java.util.Iterator#hasNext()
   */
  public boolean hasNext()
  {
    return size == null || position < size;
  }



  /**
   * Returns the next element in the iteration. Calling this method repeatedly
   * until the {@link #hasNext()} method returns false will return each element
   * in the underlying collection exactly once.
   * 
   * @return the next element in the iteration.
   * @see java.util.Iterator#next()
   */
  public CriteriaResult<MAINTYPE> next()
  {
    Object[] nextResult = scrollableResults.get();
    position++;
    if (size == null && scrollableResults.isLast()) {
      size = position;
    }
    scrollableResults.next();
    return new HibernateCriteriaResult<MAINTYPE>(hibernateCriteria, nextResult);
  }



  /**
   * Removes from the underlying collection the last element returned by the
   * iterator (optional operation). This method can be called only once per call
   * to <tt>next</tt>. The behavior of an iterator is unspecified if the
   * underlying collection is modified while the iteration is in progress in any
   * way other than by calling this method.
   * 
   * @see java.util.Iterator#remove()
   */
  public void remove()
  {
    throw new UnsupportedOperationException();
  }



  /**
   * Inserts the specified element into the list (optional operation). The
   * element is inserted immediately before the next element that would be
   * returned by <tt>next</tt>, if any, and after the next element that would
   * be returned by <tt>previous</tt>, if any. (If the list contains no
   * elements, the new element becomes the sole element on the list.) The new
   * element is inserted before the implicit cursor: a subsequent call to
   * <tt>next</tt> would be unaffected, and a subsequent call to
   * <tt>previous</tt> would return the new element. (This call increases by
   * one the value that would be returned by a call to <tt>nextIndex</tt> or
   * <tt>previousIndex</tt>.)
   * 
   * @param value
   *          the element to insert.
   * @see java.util.ListIterator#add(java.lang.Object)
   */
  public void add(CriteriaResult<MAINTYPE> value)
  {
    throw new UnsupportedOperationException();
  }



  /**
   * Returns <tt>true</tt> if this list iterator has more elements when
   * traversing the list in the reverse direction. (In other words, returns
   * <tt>true</tt> if <tt>previous</tt> would return an element rather than
   * throwing an exception.)
   * 
   * @return <tt>true</tt> if the list iterator has more elements when
   *         traversing the list in the reverse direction.
   * @see java.util.ListIterator#hasPrevious()
   */
  public boolean hasPrevious()
  {
    return position > 0;
  }



  /**
   * Returns the index of the element that would be returned by a subsequent
   * call to <tt>next</tt>. (Returns list size if the list iterator is at the
   * end of the list.)
   * 
   * @return the index of the element that would be returned by a subsequent
   *         call to <tt>next</tt>, or list size if list iterator is at end
   *         of list.
   * @see java.util.ListIterator#nextIndex()
   */
  public int nextIndex()
  {
    return position;
  }



  /**
   * Returns the previous element in the list. This method may be called
   * repeatedly to iterate through the list backwards, or intermixed with calls
   * to <tt>next</tt> to go back and forth. (Note that alternating calls to
   * <tt>next</tt> and <tt>previous</tt> will return the same element
   * repeatedly.)
   * 
   * @return the previous element in the list.
   * @see java.util.ListIterator#previous()
   */
  public CriteriaResult<MAINTYPE> previous()
  {
    scrollableResults.previous();
    position--;
    Object nextResult = scrollableResults.get();
    if (hibernateCriteria.isJoined()) {
      return new HibernateCriteriaResult<MAINTYPE>(hibernateCriteria, (Object[]) nextResult);
    }
    else {
      return new HibernateCriteriaResult<MAINTYPE>(hibernateCriteria, nextResult);
    }
  }



  /**
   * Returns the index of the element that would be returned by a subsequent
   * call to <tt>previous</tt>. (Returns -1 if the list iterator is at the
   * beginning of the list.)
   * 
   * @return the index of the element that would be returned by a subsequent
   *         call to <tt>previous</tt>, or -1 if list iterator is at
   *         beginning of list.
   * @see java.util.ListIterator#previousIndex()
   */
  public int previousIndex()
  {
    return nextIndex() - 1;
  }



  /**
   * Replaces the last element returned by <tt>next</tt> or <tt>previous</tt>
   * with the specified element (optional operation). This call can be made only
   * if neither <tt>ListIterator.remove</tt> nor <tt>ListIterator.add</tt>
   * have been called after the last call to <tt>next</tt> or
   * <tt>previous</tt>.
   * 
   * @param value
   *          the element with which to replace the last element returned by
   *          <tt>next</tt> or <tt>previous</tt>.
   * @see java.util.ListIterator#set(java.lang.Object)
   */
  public void set(CriteriaResult<MAINTYPE> value)
  {
    throw new UnsupportedOperationException();
  }

}
